package com.example.demo.utils;

import com.example.demo.model.CustomUserDetails;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultJws;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by rajeevkumarsingh on 19/08/17.
 */
@Component
public class JwtTokenProvider {

    private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);

    @Value("${app.jwtSecret}")
    private String jwtSecret;

    public String generateToken(Authentication authentication, Long jwtExpirationInMs) {

        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);

        return Jwts.builder()
                .setSubject(customUserDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUserNameFromJWT(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }

    public boolean validateToken(String authToken) {
        try {
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException ex) {
            logger.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty.");
        }
        return false;
    }

    /**
     * 解析 jwt token
     *
     * @param token 需要解析的json
     * @return
     */
    public Jws<Claims> parserAuthenticateToken(String token) {
        try {
            final Jws<Claims> claimsJws = Jwts.parser()
                    .setSigningKey(jwtSecret)
                    .parseClaimsJws(token);
            return claimsJws;
        } catch (ExpiredJwtException e) {
            return new DefaultJws(null, e.getClaims(), "");
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 判断 jwt 是否过期
     *
     * @param jws
     * @return true:过期 false:没过期
     */
    public boolean isJwtExpired(Jws<Claims> jws) {
        return jws.getBody().getExpiration().before(new Date());
    }

    /**
     * 构建认证过的认证对象
     */
    public static Authentication buildAuthentication(Jws<Claims> jws, String userIdFieldName, Set<GrantedAuthority> grantedAuthorities, String password) {
        Object userId = jws.getBody().get(userIdFieldName);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, password, grantedAuthorities);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * 生成 jwt token
     */
    public String generatorJwtToken(String username, String userIdFieldName, Long expireSecond) {
        Date expireTime = Date.from(LocalDateTime.now().plusSeconds(expireSecond).atZone(ZoneId.systemDefault()).toInstant());
        Map<String, Object> claim = new HashMap<>();
        claim.put(userIdFieldName, username);
        return Jwts.builder()
                .setSubject(username)
                .setHeaderParam("typ", "JWT")
                .setIssuedAt(new Date())
                .setExpiration(expireTime)
                .addClaims(claim)
                .signWith(SignatureAlgorithm.HS256, jwtSecret)
                .compact();
    }
}
